#include "server.h"
#include "../list/charlist.h"
#include "../list/intlist.h"
#include "../clientserver/toolsclientserver.h"
#include "serverorder.h"

void *routineThreadServer_Server(void *serv);
void *routineThreadNewConnexion_Server(void *serv);
void *routineThreadService_Server(void *serv);
/**
 * @fn int createSocket_Server(const char *port)
 * @brief Crée une socket d'attente de connexion client.
 *
 * @param port Le port d'écoute de la socket.
 * @return La socket nouvellement créée, Si la création n'est pas possible, le
 * programme est terminé avec le code erreur 1.
 */
int createSocket_Server(const char * port);

/**
 * @fn int waitConnection_Server(int socket)
 * @brief Attente d'une connexion sur la socket socket.
 *
 * @param socket La socket d'écoute.
 * @return La socket de connexion d'un client. NB : Elle est indépandante de la
 * socket d'écoute.
 */
int waitConnection_Server(int socket);


void prepare_Server(Server *server, const int nbThread, const char *port,
                    const List *lFolder, const int nbSocketAttente)
{
  server->nbSocketAttente = nbSocketAttente;
  server->socket = -1;
  server->port = (char *) malloc ((1 + strlen(port)) * sizeof(char));
  strcpy(server->port, port);
  server->nbThread = nbThread;
  server->tabThread = (pthread_t *) malloc (nbThread * sizeof(pthread_t));
  if(server->tabThread == NULL)
    {
      fprintf(stderr, "[server]Erreur allocation du tableau de thread\n");
      exit(1);
    }
  /* Initialisation de la liste qui contient les différents dossier de
   * partage */
  if(lFolder == NULL)
    initialize_List(&(server->lFolder), create_CharList, destroy_CharList,
                    compare_CharList, NULL);
  else
    initializeCopy_List(&(server->lFolder), lFolder);
}

void destroy_Server(Server *server)
{
  if(server->socket != -1)
    close_Server(server);
  free(server->port);
  free(server->tabThread);
  testament_List(&(server->lFolder));
}

ServerReturn start_Server(Server *server)
{
  int i;
  if(server->socket != -1)
    return SERVER_ACTIVE;
  /* Création de la socket de dialogue entre le programme et le server */
  if(pipe(server->pipeProgrammeServer) != 0)
    {
      perror("[prepare_Server]Erreur ouverture pipe sur "
             "socketProgrammeServer.");
      exit(1);
    }
  /* Création de la socket de dialogue entre le server et le thread utiliser
   * pour une nouvelle connexion */
  if(pipe(server->pipeServerNewConnection) != 0)
    {
      perror("[prepare_Server]Erreur ouverture pipe sur "
             "socketServerNewConnection.");
      exit(1);
    }
  /* Initialisation de la structure de Mutex pour l'ajout de client */
  initialize_MutexSocketList(&(server->mutexSocketList),
                             server->nbSocketAttente);
  server->socket = createSocket_Server(server->port);
  if(server->socket == -1)
    {
      return SERVER_ERR;
    }
  /* Création du thread server */
  if(pthread_create(&(server->threadServer), NULL, routineThreadServer_Server,
                    server) != 0)
    {
      fprintf(stderr, "[server]Erreur création du thread server\n");
      exit(1);
    }
  /* Création du thread pour les nouvelles connexion */
  if(pthread_create(&(server->threadNewConnection), NULL,
                    routineThreadNewConnexion_Server, server) != 0)
    {
      fprintf(stderr, "[server]Erreur création du thread pour les nouvelles"
              " connexion\n");
      exit(1);
    }
  /* Création des threads de service */
  for(i = 0; i < server->nbThread; i++)
    {
      if(pthread_create(&(server->tabThread[i]), NULL,
                        routineThreadService_Server, server) != 0)
	{
	  fprintf(stderr, "[server]Erreur création du thread : %d\n", i);
	  exit(1);
	}
    }
  return SERVER_ACTIVE;
}

ServerReturn close_Server(Server *server)
{
  int i = -1;
  if(server->socket == -1)
    return SERVER_CLOSE;
  /* Terminaison des threads en attente de connexion */
  notifyEnd_MutexSocketList(&(server->mutexSocketList));
  if(write(server->pipeProgrammeServer[1], &i, sizeof(i)) == -1)
    {
      perror("[close_Server]Erreur send -1 on socketProgrammeServer");
    }
  for(i = 0; i < server->nbThread; i++)
    {
      if(pthread_join(server->tabThread[i], NULL) != 0)
	{
	  fprintf(stderr, "[server]Erreur attente thread n°%d", i);
	}
    }
   if(pthread_join(server->threadNewConnection, NULL) != 0)
     {
       perror("[server]Erreur attente threadNewConnection");
     }
   if(pthread_join(server->threadServer, NULL) != 0)
     {
       perror("[server]Erreur attente threadServer");
     }
   testament_MutexSocketList(&(server->mutexSocketList));
   shutdown(server->pipeProgrammeServer[0], SHUT_RDWR);
   shutdown(server->pipeProgrammeServer[1], SHUT_RDWR);
   shutdown(server->pipeServerNewConnection[0], SHUT_RDWR);
   shutdown(server->pipeServerNewConnection[1], SHUT_RDWR);

   close(server->pipeProgrammeServer[0]);
   close(server->pipeProgrammeServer[1]);
   close(server->pipeServerNewConnection[0]);
   close(server->pipeServerNewConnection[1]);
   shutdown(server->socket, SHUT_RDWR);
   close(server->socket);
   server->socket = -1;
   return SERVER_CLOSE;
}

void *routineThreadServer_Server(void *serv)
{
  Server *server = (Server *) serv;
  int end = 1;
  fd_set rfds_tot;
  fd_set rfds;
  int socketClient;

  FD_ZERO(&rfds_tot);
  FD_SET(server->socket, &rfds_tot);
  FD_SET(server->pipeProgrammeServer[0], &rfds_tot);
  while(end)
    {
      int retval;
      FD_ZERO(&rfds);
      rfds = rfds_tot;
      retval = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
      if(retval == -1)
	{
	  perror("[routineThreadServer_Server]Erreur select");
	  exit(1);
	}
      if(FD_ISSET(server->socket, &rfds))
	{
	  socketClient = waitConnection_Server(server->socket);
	  
      if(write(server->pipeServerNewConnection[1], &socketClient,
              sizeof(int)) == -1)
	    {
          fprintf(stderr, "[server]Erreur routineThreadServer_Server sur la"
                  " socketServerNewConnection\n");
	      exit(1);
	    }
	}
      if(FD_ISSET(server->pipeProgrammeServer[0], &rfds))
	{
	  end = 0;
	}

    }
  return NULL;
}

void *routineThreadNewConnexion_Server(void *serv)
{
  Server *server = (Server *)serv;
  fd_set rfds_tot;
  fd_set rfds;
  List listSocket;
  int continu = 1;
  initialize_List(&listSocket, create_IntList, destroy_IntList, NULL, NULL);
  FD_ZERO(&rfds_tot);
  FD_SET(server->pipeServerNewConnection[0], &rfds_tot);
  FD_SET(server->pipeProgrammeServer[0], &rfds_tot);
  while(continu)
    {
      int ret;
      int socket;
      rfds = rfds_tot;
      ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
      if(ret == -1)
	{
	  perror("[routineThreadClient]Erreur select");
	  exit(1);
	}
      if(FD_ISSET(server->pipeServerNewConnection[0], &rfds))
	{
      if(read(server->pipeServerNewConnection[0], &socket,
              sizeof(int)) == -1)
	    {
          fprintf(stderr, "[routineThreadNewConnexion_Server]Erreur recv"
                  " socketServerNewConnection");
	      socket = -1;
	    }
	  if(socket > 0)
	    {
	      beginingDealing_Server(server, socket);
	    }
	}
      if(FD_ISSET(server->pipeProgrammeServer[0], &rfds))
	{
	  continu = 0;
	}
    }
  return NULL;
}

void *routineThreadService_Server(void *serv)
{
  Server *server = (Server *) serv;
  int continu = 1;
  int socket;
  int ret;
  fd_set rfds_tot;
  fd_set rfds;

  FD_ZERO(&rfds_tot);
  FD_SET(server->pipeProgrammeServer[0], &rfds_tot);
  while(continu)
    {      
      socket = getFirst_MutexSocketList(&(server->mutexSocketList));
      if(socket < 0)
	continu = 0;
      else
      {
	FD_SET(socket, &rfds_tot);
	rfds = rfds_tot;
	ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
	if(ret == -1)
	  {
	    perror("[routineThreadClient]Erreur select");
	    exit(1);
	  }
	if(FD_ISSET(socket, &rfds))
	  {
	    if(dealing_Server(socket, server) == SERVERORDERRETURN_SUCCESS)
	      {
		int ret;
		ret = insert_MutexSocketList(&(server->mutexSocketList), &socket);
		if(ret == MUTEXSOCKETLIST_ERR)
		  {
            fprintf(stderr, "[routineThreadService_Server]Erreur"
                    " insert_MutexList\n");
		  }
	      }
	    else
	      subtractConnection_MutexSocketList(&(server->mutexSocketList));
	  }
    if(FD_ISSET(server->pipeProgrammeServer[0], &rfds))
	  {
	    quit_ServerOrder(socket);
	  }
	FD_CLR(socket, &rfds_tot);
      }
    }
  return NULL;
}

int createSocket_Server(const char * port) {
  struct addrinfo hints;
  struct addrinfo * result, * rp;
  int res, bon, sock_err, s;
  int yes = 1;

  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;

  res = getaddrinfo(NULL, port, &hints, &result);
  if(res != 0)
    {
      fprintf(stderr, "[createSocket]getaddinfo:%s\n",gai_strerror(res));
      return -1;
    }

  bon = 0;
  for( rp = result; rp != NULL; rp = rp->ai_next){
    s = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    if( s == -1)
      {
	fprintf(stderr, "[createSocket]Création de la socket impossible\n");
	continue;
      }
    if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR,(char *) &yes, sizeof(int)) == -1) {
      return -1;
    }
    res = bind(s , rp->ai_addr, rp->ai_addrlen);
    if (res == 0)
      {
	bon = 1;
	break;
      }
    perror("");
    fprintf(stderr, "[createSocket]Impossible de réserver l'adresse %d\n",res);
    close(s);
  }
  freeaddrinfo(result);
  if (bon == 0)
    {
      fprintf(stderr, "[createSocket]Impossible d'obtenir une adresse\n");
      return -1;
    }

  sock_err = listen(s, 5);

  if(sock_err == -1)
    {
      perror("listen");
      close(s);
      return -1;
    }

  return s;
}

int waitConnection_Server(int socket)
{
  int t, res;
  struct sockaddr_storage taddr;
  socklen_t taddrlen = sizeof(taddr);
  char hname[NI_MAXHOST], sname [NI_MAXSERV];

  t = accept(socket, (struct sockaddr *)&taddr, &taddrlen);
  res = getnameinfo((struct sockaddr *)&taddr, taddrlen,
		    hname, NI_MAXHOST,
		    sname, NI_MAXSERV,
		    NI_NUMERICSERV);
  if(res !=0) {
    fprintf(stderr, "[waitConnection]getnameinfo : %s\n", gai_strerror(res));
    exit(1);
  }
  return t;
}

const char *getPort_Server(Server *server)
{
    return server->port;
}

void setPort_Server(Server *server, const char *port)
{
    if(port == NULL)
        return;
    if(server->port != NULL)
    {
        free(server->port);
        server->port = NULL;
    }
    server->port = (char *) malloc ((1 + strlen(port)) * sizeof(char));
    strcpy(server->port, port);
}

int getThread_Server(Server *server)
{
    return server->nbThread;
}

void setThread_Server(Server *server, const int nbThread)
{
    if(nbThread > 0)
        server->nbThread = nbThread;
}
